/*
 * Copyright 2019 The ZuSmart Project
 *  
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *  
 *      http://www.apache.org/licenses/LICENSE-2.0
 *  
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.zusmart.base.future.support;

import java.util.LinkedList;
import java.util.Queue;
import java.util.concurrent.TimeUnit;

import com.zusmart.base.future.Future;
import com.zusmart.base.future.FutureListener;
import com.zusmart.base.logging.Logger;
import com.zusmart.base.logging.LoggerFactory;
import com.zusmart.base.util.Assert;

/**
 * @author Administrator
 *
 */
public abstract class AbstractFuture<F extends Future<F, L>, L extends FutureListener<F>> implements Future<F, L> {

	private static final Logger logger = LoggerFactory.getLogger(AbstractFuture.class);

	private volatile boolean completed = false;
	private volatile boolean successed = false;

	private volatile Throwable cause;
	private volatile Queue<L> listeners;

	@Override
	public void waitComplete() {
		if (!this.completed) {
			synchronized (this) {
				while (!this.completed) {
					try {
						this.wait();
					} catch (InterruptedException e) {
						// IGNORE
					}
				}
			}
		}
	}

	@Override
	public void waitComplete(int timeoutMilliSecond) {
		this.waitComplete(timeoutMilliSecond, TimeUnit.MILLISECONDS);
	}

	@Override
	public void waitComplete(int timeout, TimeUnit timeUnit) {
		Assert.isTrue(timeout <= 0, "timeout must > 0");
		if (null == timeUnit) {
			timeUnit = TimeUnit.MICROSECONDS;
		}
		long timeoutValue = timeUnit.toMillis(timeout);
		if (!this.completed) {
			synchronized (this) {
				try {
					this.wait(timeoutValue);
				} catch (InterruptedException e) {
					// IGNORE
				}
				this.dispatch(this.listeners);
			}
		}
	}

	@Override
	public boolean isCompleted() {
		return this.completed;
	}

	@Override
	public boolean isSuccessed() {
		return this.successed;
	}

	@Override
	public void attachListener(L listener) {
		if (null != listener) {
			synchronized (this) {
				if (this.completed) {
					this.dispatch(this.getFuture(), listener);
				} else {
					if (null == this.listeners) {
						this.listeners = new LinkedList<L>();
					}
					this.listeners.add(listener);
				}
			}
		}
	}

	@Override
	public void detachListener(L listener) {
		synchronized (this) {
			if (!this.completed && null != this.listeners) {
				this.listeners.remove(listener);
			}
		}
	}

	@Override
	public void setSuccess() {
		this.setSuccessed(true, null);
	}

	@Override
	public void setFailure(Throwable cause) {
		this.setSuccessed(false, cause);
	}

	@Override
	public Throwable getFailureCause() {
		return this.cause;
	}

	@Override
	public String toString() {
		return String.format("[%s] completed = %s , successed = %s ", super.toString(), this.isCompleted(), this.isSuccessed());
	}

	protected synchronized void setSuccessed(boolean successed, Throwable cause) {
		if (!this.completed) {
			this.completed = true;
			this.successed = successed;
			this.notifyAll();
			this.dispatch(this.listeners);
		}
	}

	protected void dispatch(Queue<L> listeners) {
		if (null == listeners || listeners.size() == 0) {
			return;
		}
		F future = this.getFuture();
		for (L listener : listeners) {
			if (null != listener) {
				this.dispatch(future, listener);
			}
		}
	}

	protected void dispatch(F future, L listener) {
		try {
			listener.execute(future);
		} catch (Exception e) {
			logger.error("execute future failed : {}", e.getMessage(), e);
		}
	}

	protected abstract F getFuture();

}